package reservator.client;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.LinkedHashSet;
import java.util.Scanner;

import reservator.common.Journey;
import reservator.common.Transport;
import reservator.server.Database;

/**
 * This class is the menu engine of the client. It display choices
 * and is "connected" to the server
 * @author Pascal Cuisinaud, Thomas Di'Meco
 */
public class MenuEngine
{
    private Database db;
    
    /**
     * Default constructor: init the client session
     */
    public MenuEngine ()
    {
        // "Connect" to the database
        db = Database.getInstance();
    }
    
    /**
     * Start the user interface
     */
    public void startProgram ()
    {
        // Load database from file
        db.revert();
        
        // Display the start menu while the user wants to quit the program
        for (; !displayStartMenu();) db.commit();
    }

    /**
     * Display the start menu
     * @return true if the user wants to quit the program
     */
    private boolean displayStartMenu ()
    {
        System.out.println("=== MAIN MENU ===");
        
        boolean quit = false;
        
        int choice = displayChoiceMenu( new String[]{
                "Add transport",
                "Edit transport",
                "Search transport",
                "Add journey",
                "Edit journey",
                "Search journey",
                "Clear database",
                "Quit"
        });
        
        // Choice selection
        switch (choice)
        {
            case 1: displayAddTransportMenu();    break;
            case 2: displayEditTransportMenu();   break;
            case 3: displaySearchTransportMenu(); break;
            case 4: displayAddJourneyMenu();      break;
            case 5: displayEditJourneyMenu();     break;
            case 6: displaySearchJourneyMenu();   break;
            case 7: clearDatabase();              break;
            case 8: quit = true;                  break;
        }
        
        return quit;
    }

    /**
     * Display the add transport menu
     */
    private void displayAddTransportMenu ()
    {
        System.out.println("=== ADD TRANSPORT ===");
        
        Transport newTransport = promptTransport(false);
        newTransport.setId(db.getNextTransportId());
        
        if ( db.addTransport(newTransport) )
            System.out.println("Transport added!");
        else
            System.out.println("Error: Transport not added!");
        
        waitReturn(); // Pause
    }
    
    /**
     * Display the edit transport menu
     */
    private void displayEditTransportMenu ()
    {
        System.out.println("=== EDIT TRANSPORT ===");
        
        // Get transport id the user wants to edit
        int id = promptInt("Transport id: ", false);
        
        LinkedHashSet<Transport> list =
                db.searchTransport(new Transport(id, null, null, 0));
        
        // Check the transport exists
        if (list.size() != 1)
        {
            System.out.println("Unable to find the transport!");
            waitReturn(); // Pause
            return;
        }
        
        Transport newTransport = promptTransport(false);
        Transport transport = (Transport) list.toArray()[0];
        
        transport.setName( newTransport.getName() );
        transport.setType( newTransport.getType() );
        transport.setCapacity( newTransport.getCapacity() );
 
        System.out.println("Transport edited!");
        waitReturn(); // Pause
    }
    
    /**
     * Display the search transport menu
     */
    private void displaySearchTransportMenu ()
    {
        System.out.println(
                "=== SEARCH TRANSPORT ===\n" +
                "Leave empty to ignore the property"
        );
        
        Transport transport = promptTransport(true);
        LinkedHashSet<Transport> list = db.searchTransport(transport);
        
        for (Transport t : list) System.out.println("    " + t);

        waitReturn(); // Pause
    }
    
    /**
     * Display the add journey menu
     */
    private void displayAddJourneyMenu ()
    {
        System.out.println("=== ADD JOURNEY ===");
        
        Journey newJourney = promptJourney(false);
        newJourney.setId(db.getNextJourneyId());
        
        if ( db.addJourney(newJourney) )
            System.out.println("Journey added!");
        else
            System.out.println("Error: Journey not added!");
        
        waitReturn(); // Pause
    }
    
    /**
     * Display the edit journey menu
     */
    private void displayEditJourneyMenu ()
    {
        System.out.println("=== EDIT JOURNEY ===");
        
        // Get journey id the user wants to edit
        int id = promptInt("Journey id: ", false);
        
        LinkedHashSet<Journey> list =
                db.searchJourney(new Journey(id, null, null, null, null, null));
        
        // Check the journey exists
        if (list.size() != 1)
        {
            System.out.println("Unable to find the journey!");
            waitReturn(); // Pause
            return;
        }
        
        Journey newJourney = promptJourney(false);
        Journey journey = (Journey) list.toArray()[0];
        
        journey.setStartPlace( newJourney.getStartPlace() );
        journey.setEndPlace( newJourney.getEndPlace() );
        journey.setStartDate( newJourney.getStartDate() );
        journey.setEndDate( newJourney.getEndDate() );
        journey.setTransport( newJourney.getTransport() );
 
        System.out.println("Journey edited!");
        waitReturn(); // Pause
    }
    
    /**
     * Display the search journey menu
     */
    private void displaySearchJourneyMenu ()
    {
        System.out.println(
                "=== SEARCH JOURNEY ===\n" +
                "Leave empty to ignore the property"
        );
        
        Journey journey = promptJourney(true);
        LinkedHashSet<Journey> list = db.searchJourney(journey);
        
        for (Journey j : list) System.out.println("    " + j);

        waitReturn(); // Pause
    }
    
    /**
     * Clear the database
     */
    private void clearDatabase ()
    {
        db.clear();
        System.out.println("Database cleaned!");
        waitReturn(); // Pause
    }

    /**
     * Display un choice menu with the given choices
     * @param choices Choice array
     * @return The choice number
     */
    private int displayChoiceMenu (String[] choices)
    {
        String text = "";
        
        // Display choices
        for (int i = 0; i < choices.length; ++i)
            text += "    " + (i+1) + ". " + choices[i] + "\n";
        
        System.out.print(text);
        
        int choice = 0;
        
        for (boolean ok = false; !ok;)
        {   
            choice = promptInt("Your choice: ", false);
            
            if (choice > 0 && choice <= choices.length) ok = true;
            else System.out.println("Invalid choice!");
        }
        
        return choice;
    }
    
    /**
     * Prompt for a transort
     * @param allowNulls true if you want to allow null for properties
     * @return The transport newly created
     */
    private Transport promptTransport (boolean allowNulls)
    {
        int    id       = 0;
        String type     = promptString("Type: ", allowNulls);
        String name     = promptString("Name: ", allowNulls);
        int    capacity = promptInt("Capacity: ", allowNulls);
        
        return new Transport(id, type, name, capacity);
    }
    
    /**
     * Prompt for a journey
     * @param allowNulls true if you want to allow null for properties
     * @return The journey newly created
     */
    private Journey promptJourney (boolean allowNulls)
    {
        int      id          = 0;
        String   startPlace  = promptString("Start place: ", allowNulls);
        String   endPlace    = promptString("End place: ", allowNulls);
        Calendar startDate   = promptDate("Start date (DD/MM/YYYY): ", allowNulls);
        Calendar endDate     = promptDate("End place (DD/MM/YYYY): ", allowNulls);
        int      tId         = promptInt("Transport Id: ", allowNulls);
        
        // Check transport id
        Transport t = null;
        
        if (allowNulls && tId != 0)
        {
            LinkedHashSet<Transport> list = db.searchTransport(new Transport(tId, null, null, 0));
            t = (list.size() == 1 ? (Transport)list.toArray()[0] : new Transport());
        }
        
        return new Journey(id, startPlace, endPlace, startDate, endDate, t);
    }
    
    /**
     * Prompt for an integer
     * @param prompt    Prompt text
     * @param allowNull true if you allow null
     * @return Integer entered by the user
     */
    private int promptInt (String prompt, boolean allowNull)
    {
        int i = 0;
        Scanner sc = new Scanner(System.in);
        
        for (boolean ok = false; !ok;)
        {
            System.out.print(prompt);
            
            try
            {
                String line = sc.nextLine();
                if (line.isEmpty() && allowNull) return 0;
                
                // Try to parse the integer
                i = Integer.parseInt(line);
                
                if (i == 0 && !allowNull)
                    System.out.println("Null integer!");
                else
                    ok = true;
            }
            catch (NumberFormatException exc)
            {
                System.out.println("Invalid format!");
            }
        }
        
        return i;
    }
    
    /**
     * Prompt for a string
     * @param prompt Prompt text
     * @param allowNull true if you allow null
     * @return String entered by the user
     */
    private String promptString (String prompt, boolean allowNull)
    {
        String s = null;
        Scanner sc = new Scanner(System.in);
        
        for (boolean ok = false; !ok;)
        {
            System.out.print(prompt);
            s = sc.nextLine();
            
            if (s.isEmpty() && allowNull)
                return null;
            else if (s.isEmpty() && !allowNull)
                System.out.println("Empty string!");
            else
                ok = true;
        }

        return s;
    }
    
    /**
     * Prompt for a date (DD/MM/YYYY)
     * @param prompt Prompt text
     * @param allowNull true if you allow null
     * @return Date entered by the user
     */
    private Calendar promptDate (String prompt, boolean allowNull)
    {
        String s = null;
        Calendar cal = new GregorianCalendar();
        Scanner sc = new Scanner(System.in);
        
        for (boolean ok = false; !ok;)
        {
            System.out.print(prompt);
            s = sc.nextLine();
            
            if (s.isEmpty() && allowNull)
                return null;
            else if (s.isEmpty() && !allowNull)
                System.out.println("Empty date!");
            else
            {
                String[] tab = s.split("/");
                
                if (tab.length != 3) System.out.println("Invalid date!");
                else
                {
                    try {
                        cal.set(Integer.valueOf(tab[2]),
                                Integer.valueOf(tab[1]),
                                Integer.valueOf(tab[0]));

                        ok = true;
                    }
                    catch (NumberFormatException exc)
                    {
                        System.out.println("Invalid date!");
                    }
                }
            }
        }

        return cal;
    }
    
    /**
     * Do a pause by waiting user carrier return
     */
    private void waitReturn ()
    {
        Scanner sc = new Scanner(System.in);
        sc.nextLine();
    }
}
